package com.north.light.musiclibrary.playback.player;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.net.Uri;
import android.text.TextUtils;

import com.google.android.exoplayer2.*;
import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.upstream.DataSource;
import com.north.light.musiclibrary.service.LibPlayMusicService;
import com.north.light.musiclibrary.aidl.model.LibPlayMusicSongInfo;
import com.north.light.musiclibrary.cache.LibPlayMusicCacheConfig;
import com.north.light.musiclibrary.cache.LibPlayMusicCacheUtils;
import com.north.light.musiclibrary.constans.LibPlayMusicState;
import com.north.light.musiclibrary.manager.LibPlayMusicFocusAndLockManager;
import com.north.light.musiclibrary.utils.LibPlayMusicBaseUtil;
import com.north.light.musiclibrary.utils.LibPlayMusicLogUtil;
import com.north.light.musiclibrary.utils.LibPlayMusicSPUtils;
import com.north.light.musiclibrary.videocache.HttpProxyCacheServer;

import static com.google.android.exoplayer2.C.CONTENT_TYPE_MUSIC;
import static com.google.android.exoplayer2.C.USAGE_MEDIA;
import static com.north.light.musiclibrary.constans.LibPlayMusicConstans.play_back_pitch;
import static com.north.light.musiclibrary.constans.LibPlayMusicConstans.play_back_speed;
import static com.north.light.musiclibrary.manager.LibPlayMusicFocusAndLockManager.AUDIO_NO_FOCUS_CAN_DUCK;
import static com.north.light.musiclibrary.manager.LibPlayMusicFocusAndLockManager.AUDIO_NO_FOCUS_NO_DUCK;
import static com.north.light.musiclibrary.manager.LibPlayMusicFocusAndLockManager.VOLUME_DUCK;
import static com.north.light.musiclibrary.manager.LibPlayMusicFocusAndLockManager.VOLUME_NORMAL;

/**
 * Created by xian on 2018/1/20.
 */

public class LibPlayMusicExoPlayback implements LibPlayMusicPlayback, LibPlayMusicFocusAndLockManager.AudioFocusChangeListener {

    private final ExoPlayerEventListener mEventListener = new ExoPlayerEventListener();
    private final IntentFilter mAudioNoisyIntentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
    private boolean isOpenCacheWhenPlaying = false;
    private boolean mPlayOnFocusGain;
    private boolean mAudioNoisyReceiverRegistered;
    private String mCurrentMediaId; //当前播放的媒体id
    private LibPlayMusicSongInfo mCurrentMediaSongInfo;
    private SimpleExoPlayer mExoPlayer;
    private boolean mExoPlayerNullIsStopped = false;
    private boolean isGiveUpAudioFocusManager = false;
    private LibPlayMusicFocusAndLockManager mFocusAndLockManager;
    private Callback mCallback;
    private Context mContext;
    private final BroadcastReceiver mAudioNoisyReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
                if (isPlaying()) {
                    Intent i = new Intent(context, LibPlayMusicService.class);
                    mContext.startService(i);
                }
            }
        }
    };
    private DataSource.Factory dataSourceFactory;
    private HttpProxyCacheServer mProxyCacheServer;
    private HttpProxyCacheServer.Builder builder;
    private long mErrorProgress = 0;
    private boolean isStateError = false;

    public LibPlayMusicExoPlayback(Context context, LibPlayMusicCacheConfig cacheConfig, boolean isGiveUpAudioFocusManager) {
        this.mContext = context;
        this.isGiveUpAudioFocusManager = isGiveUpAudioFocusManager;
        mFocusAndLockManager = new LibPlayMusicFocusAndLockManager(context, this);
        dataSourceFactory = LibPlayMusicExoPlayerHelper.getInstance().buildDataSourceFactory();

        builder = LibPlayMusicCacheUtils.createHttpProxyCacheServerBuilder(mContext, cacheConfig);
        if (cacheConfig != null && cacheConfig.isOpenCacheWhenPlaying()) {
            isOpenCacheWhenPlaying = true;
        }
        mProxyCacheServer = builder.build();
    }

    /**
     * 释放服务使用的资源进行播放，这主要只是WiFi锁本地播放。 如果请求，ExoPlayer实例也被释放。
     *
     * @param releasePlayer 指示播放器是否也应该被释放
     */
    private void releaseResources(boolean releasePlayer, boolean isResetPlayer) {
        if (releasePlayer && mExoPlayer != null) {
            mExoPlayer.release();
            mExoPlayer.removeListener(mEventListener);
            if (!isResetPlayer) {
                mExoPlayerNullIsStopped = true;
            }
            mPlayOnFocusGain = false;
            mExoPlayer = null;
        }
        mFocusAndLockManager.releaseWifiLock();
    }

    @Override
    public void start() {

    }

    @Override
    public void stop(boolean notifyListeners, boolean isResetPlayer) {
        mFocusAndLockManager.giveUpAudioFocus();
        unregisterAudioNoisyReceiver();
        releaseResources(true, isResetPlayer);
    }

    @Override
    public int getState() {
        //STATE_IDLE      没有任何媒体播放。
        //STATE_BUFFERING 无法立即从当前位置进行播放
        //STATE_READY     可以从当前位置立即进行播放。 如果  {@link #getPlayWhenReady（）}为true，立即播放，否则暂停。
        //STATE_ENDED     已经完成播放媒体。
//        LogUtil.i("thread = " + Thread.currentThread());
        int state = LibPlayMusicState.STATE_IDLE;
        if (isStateError) {
            state = LibPlayMusicState.STATE_ERROR;
        } else {
            if (mExoPlayer == null) {
                state = mExoPlayerNullIsStopped ? LibPlayMusicState.STATE_STOP : LibPlayMusicState.STATE_IDLE;
            } else {
                switch (mExoPlayer.getPlaybackState()) {
                    case Player.STATE_IDLE:
                    case Player.STATE_ENDED:
                        state = LibPlayMusicState.STATE_IDLE;
                        break;
                    case Player.STATE_BUFFERING:
                        state = LibPlayMusicState.STATE_ASYNC_LOADING;
                        break;
                    case Player.STATE_READY:
                        state = mExoPlayer.getPlayWhenReady() ? LibPlayMusicState.STATE_PLAYING : LibPlayMusicState.STATE_PAUSED;
                        break;
                }
            }
        }
        return state;
    }

    @Override
    public void setState(int state) {

    }

    @Override
    public boolean isConnected() {
        return true;
    }

    @Override
    public boolean isPlaying() {
        return mPlayOnFocusGain || (mExoPlayer != null && mExoPlayer.getPlayWhenReady());
    }

    @Override
    public long getCurrentStreamPosition() {
        return mExoPlayer != null ? mExoPlayer.getCurrentPosition() : 0;
    }

    @Override
    public long getBufferedPosition() {
        long bufferedPosition = mExoPlayer != null ? mExoPlayer.getBufferedPosition() : 0;
        long duration = mExoPlayer != null ? mExoPlayer.getDuration() : 0;
        bufferedPosition = bufferedPosition * 2;
        if (bufferedPosition > duration) {
            bufferedPosition = duration;
        }
        if (isOpenCacheWhenPlaying && mCurrentMediaSongInfo != null) {
            boolean fullyCached = mProxyCacheServer.isCached(mCurrentMediaSongInfo.getSongUrl());
            return fullyCached ? duration : bufferedPosition;
        } else {
            return bufferedPosition;
        }
    }

    @Override
    public void updateLastKnownStreamPosition() {

    }

    @Override
    public void play(LibPlayMusicSongInfo info) {
        mPlayOnFocusGain = true;
        mFocusAndLockManager.tryToGetAudioFocus();
        registerAudioNoisyReceiver();
        String mediaId = info.getSongId();
        boolean mediaHasChanged = !TextUtils.equals(mediaId, mCurrentMediaId);
        if (mediaHasChanged) {
            mCurrentMediaId = mediaId;
            mCurrentMediaSongInfo = info;
        }
        if (mediaHasChanged || mExoPlayer == null) {
            releaseResources(false, false); // release everything except the player

            String source = info.getSongUrl();
            if (source != null && LibPlayMusicBaseUtil.isOnLineSource(source)) {
                source = source.replaceAll(" ", "%20"); // Escape spaces for URLs
            }
            if (TextUtils.isEmpty(source)) {
                if (mCallback != null) {
                    mCallback.onError("song url is null");
                }
                return;
            }

            Uri playUri;
            if (LibPlayMusicBaseUtil.isOnLineSource(source)) {
                String proxyUrl;
                if (isOpenCacheWhenPlaying && (LibPlayMusicExoPlayerHelper.getInstance().getMediaType(null, Uri.parse(source)) == C.TYPE_OTHER)) {
                    boolean isRtmpSource = source.toLowerCase().startsWith("rtmp://");
                    proxyUrl = isRtmpSource ? source : mProxyCacheServer.getProxyUrl(source);
                } else {
                    proxyUrl = source;
                }
                playUri = Uri.parse(proxyUrl);

            } else {
                playUri = LibPlayMusicBaseUtil.getLocalSourceUri(source);
            }
            if (playUri == null) {
                if (mCallback != null) {
                    mCallback.onError("song uri is null");
                }
                return;
            }

            //LogUtil.i("isOpenCacheWhenPlaying = " + isOpenCacheWhenPlaying + " playUri = " + playUri.toString());

            if (mExoPlayer == null) {
                mExoPlayer = ExoPlayerFactory.newSimpleInstance(mContext, new DefaultRenderersFactory(mContext),
                        new DefaultTrackSelector(), new DefaultLoadControl());
                mExoPlayer.addListener(mEventListener);
                changePlaybackParameters();
            }

            final AudioAttributes audioAttributes = new AudioAttributes.Builder()
                    .setContentType(CONTENT_TYPE_MUSIC)
                    .setUsage(USAGE_MEDIA)
                    .build();
            mExoPlayer.setAudioAttributes(audioAttributes);

            MediaSource mediaSource = LibPlayMusicExoPlayerHelper.getInstance().buildMediaSource(dataSourceFactory, playUri, null);
            mExoPlayer.prepare(mediaSource);
            mFocusAndLockManager.acquireWifiLock();
        }
        configurePlayerState();
    }

    private void changePlaybackParameters() {
        float spSpeed = (float) LibPlayMusicSPUtils.get(mContext, play_back_speed, 1f);
        float spPitch = (float) LibPlayMusicSPUtils.get(mContext, play_back_pitch, 1f);
        float currSpeed = mExoPlayer.getPlaybackParameters().speed;
        float currPitch = mExoPlayer.getPlaybackParameters().pitch;
        if (spSpeed != currSpeed || spPitch != currPitch) {
            setPlaybackParameters(spSpeed, spPitch);
        }
    }

    @Override
    public void pause() {
        if (mExoPlayer != null) {
            mExoPlayer.setPlayWhenReady(false);
        }
//        mFocusAndLockManager.giveUpAudioFocus();
        releaseResources(false, false);
        unregisterAudioNoisyReceiver();
    }

    @Override
    public void seekTo(long position) {
        if (mExoPlayer != null) {
            registerAudioNoisyReceiver();
            mExoPlayer.seekTo(position);
        } else {
            if (mCurrentMediaSongInfo != null && mExoPlayer != null) {
                play(mCurrentMediaSongInfo);

                mExoPlayer.seekTo(position);
            }
        }
    }

    @Override
    public String getCurrentMediaId() {
        return mCurrentMediaId;
    }

    @Override
    public void setCurrentMediaId(String mediaId) {
        this.mCurrentMediaId = mediaId;
    }

    @Override
    public int getDuration() {
        return mExoPlayer != null ? (int) mExoPlayer.getDuration() : 0;
    }

    @Override
    public void setErrorProgress(int errorProgress) {
        mErrorProgress = errorProgress;
    }

    @Override
    public LibPlayMusicSongInfo getCurrentMediaSongInfo() {
        return mCurrentMediaSongInfo;
    }

    @Override
    public void openCacheWhenPlaying(boolean isOpen) {
        isOpenCacheWhenPlaying = isOpen;
    }

    @Override
    public void setPlaybackParameters(float speed, float pitch) {
        if (mExoPlayer != null) {

            if(!isPlaying()){
                mExoPlayer.setPlayWhenReady(true);
            }


            PlaybackParameters parameters =  new PlaybackParameters(speed, pitch);
            mExoPlayer.setPlaybackParameters(parameters);


        }
    }

    @Override
    public void setVolume(float audioVolume) {
        if (mExoPlayer != null) {
            mExoPlayer.setVolume(audioVolume);
        }
    }

    @Override
    public int getAudioSessionId() {
        if (mExoPlayer != null) {
            return mExoPlayer.getAudioSessionId();
        }
        return 0;
    }

    @Override
    public float getPlaybackSpeed() {
        if (mExoPlayer != null) {
            return mExoPlayer.getPlaybackParameters().speed;
        } else {
            return (float) LibPlayMusicSPUtils.get(mContext, play_back_speed, 1f);
        }
    }

    @Override
    public float getPlaybackPitch() {
        if (mExoPlayer != null) {
            return mExoPlayer.getPlaybackParameters().pitch;
        } else {
            return (float) LibPlayMusicSPUtils.get(mContext, play_back_pitch, 1f);
        }
    }

    @Override
    public void setCallback(Callback callback) {
        this.mCallback = callback;
    }

    /**
     * 根据音频焦点设置重新配置播放器并启动/重新启动。 这种方法
     * 启动/重新启动ExoPlayer的实例尊重当前的音频焦点状态。 所以，如果我们
     * 有焦点，会正常播放; 如果我们没有重点，它会离开玩家
     * 暂停或将其设置为低音量，具体取决于当前焦点允许的内容
     * 设置。    
     */
    private void configurePlayerState() {
        if (mFocusAndLockManager.getCurrentAudioFocusState() == AUDIO_NO_FOCUS_NO_DUCK) {
            if (!isGiveUpAudioFocusManager) {
                pause();
            }
        } else {
            registerAudioNoisyReceiver();
            if (mFocusAndLockManager.getCurrentAudioFocusState() == AUDIO_NO_FOCUS_CAN_DUCK) {
                mExoPlayer.setVolume(VOLUME_DUCK);
            } else {
                mExoPlayer.setVolume(VOLUME_NORMAL);
            }
            if (mPlayOnFocusGain) {
                mExoPlayer.setPlayWhenReady(true);
                mPlayOnFocusGain = false;
            }
            if (mExoPlayerNullIsStopped) {
                mExoPlayerNullIsStopped = false;
            }
            if (mErrorProgress != 0) {
                seekTo(mErrorProgress);
                mErrorProgress = 0;
            }
        }
    }

    private void registerAudioNoisyReceiver() {
        if (!mAudioNoisyReceiverRegistered) {
            mContext.registerReceiver(mAudioNoisyReceiver, mAudioNoisyIntentFilter);
            mAudioNoisyReceiverRegistered = true;
        }
    }

    private void unregisterAudioNoisyReceiver() {
        if (mAudioNoisyReceiverRegistered) {
            mContext.unregisterReceiver(mAudioNoisyReceiver);
            mAudioNoisyReceiverRegistered = false;
        }
    }

    @Override
    public void onAudioFocusLossTransient() {
        mPlayOnFocusGain = mExoPlayer != null && mExoPlayer.getPlayWhenReady();
    }

    @Override
    public void onAudioFocusChange() {
        if (mExoPlayer != null) {
            configurePlayerState();
        }
    }


    /**
     * ExoPlayer事件监听器
     */
    private final class ExoPlayerEventListener implements Player.EventListener {


        @Override
        public void onTimelineChanged(Timeline timeline, Object manifest, int reason) {

        }

        @Override
        public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {

        }

        @Override
        public void onLoadingChanged(boolean isLoading) {

        }

        @Override
        public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
            isStateError = false;
            if (mCallback != null) {
                switch (playbackState) {
                    case Player.STATE_IDLE:
                        mCallback.onPlaybackStatusChanged(LibPlayMusicState.STATE_IDLE);
                        break;
                    case Player.STATE_BUFFERING:
                        mCallback.onPlaybackStatusChanged(LibPlayMusicState.STATE_ASYNC_LOADING);
                        break;
                    case Player.STATE_READY:
                        mCallback.onPlaybackStatusChanged(playWhenReady ? LibPlayMusicState.STATE_PLAYING : LibPlayMusicState.STATE_PAUSED);
                        break;
                    case Player.STATE_ENDED:
                        mCallback.onPlayCompletion(mCurrentMediaSongInfo);
                        // mCallback.onPlaybackStatusChanged(State.STATE_ENDED);
                        break;
                }
            }
        }

        @Override
        public void onRepeatModeChanged(int repeatMode) {

        }

        @Override
        public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {

        }

        @Override
        public void onPlayerError(ExoPlaybackException error) {
            mCurrentMediaId = "";
            mErrorProgress = getCurrentStreamPosition();
            isStateError = true;
            final String what;
            switch (error.type) {
                case ExoPlaybackException.TYPE_SOURCE:
                    what = error.getSourceException().getMessage();
                    break;
                case ExoPlaybackException.TYPE_RENDERER:
                    what = error.getRendererException().getMessage();
                    break;
                case ExoPlaybackException.TYPE_UNEXPECTED:
                    what = error.getUnexpectedException().getMessage();
                    break;
                default:
                    what = "Unknown: " + error;
            }
            if (mCallback != null) {
                mCallback.onError("ExoPlayer error " + what);
            }
        }

        @Override
        public void onPositionDiscontinuity(int reason) {

        }


        @Override
        public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
            LibPlayMusicLogUtil.i("onPlaybackParametersChanged:"+playbackParameters.speed);
        }

        @Override
        public void onSeekProcessed() {

        }
    }


}
